Java基础扫盲系列(三) 您所在的位置:网站首页 内省 java Java基础扫盲系列(三)

Java基础扫盲系列(三)

2023-04-17 19:30| 来源: 网络整理| 查看: 265

前言

Java内省技术属于Java基础体系的的一部分,但是很多人都不甚了解。笔者也是在学习Spring源码的过程中遇到该技术模块的。为了完善技术体系,本文将全面的学习该技术。在提到Java内省技术,就不得不说Java的反射和JavaBeans技术,相信这两点大家应该都非常熟悉。本文将会从以下几个方面学习Java内省:

Java内省和JavaBeans技术 Java内省和反射技术的关系 Java内省的API介绍 Java内省实战 ### Java内省和JavaBeans技术

在JavaBeans 101的规范中对于JavaBeans是这样定义的:

“A Java Bean is a reusable software component that can be manipulated visually in a builder tool.”

JavaBeans是一种可重复使用的软件组件,并且可以通过可视化的工具的方式进行操作。

如在NetBeans IDE中创建一个Button按钮的JavaBeans如下:

以上的Pannel面板中的Button按钮就是一个JavaBean,满足可重复使用的定义。通过点击该按钮将会展示该Button的可视化设计器:

在设计器中可以编辑该Button的Property和Event。这样就完成了一个JavaBean的创建。

Note: 关于NetBeans的创建可视化Button Bean的过程这里不再详细介绍,如果感兴趣可以参考:The Java™ Tutorials

当然这是Java Client的可视化编程,可以这样做。但是对于Java服务端应用而言,却无法这样使用。且站在程序猿的角度,更偏向使用coding方式解决问题。

使用coding方式编写JavaBean组件也非常容易,不需要实现任何借口或者任何特定的工具。但是JavaBean有规范约束,主要从Propeties,Method和Events三个方面约定。只有这样遵循这些规定,Bean的构建工具才可以探测检视JavaBean,再以可视化方式展示JavaBean。

Properties

属性由公共的Setter和Getter方法定义。对于可视化工具(如NetBeans),将能识别方法名称,然后将该属性展示。如下:

public class FaceBean { private int mMouthWidth = 90; // getter for mMouthWidth public int getMouthWidth() { return mMouthWidth; } // setter for mMouthWidth public void setMouthWidth(int mw) { mMouthWidth = mw; } }

关于属性的种类非常多,如:Indexed Properties,Bound Properties等等。如果感兴趣可以参考:The Java™ Tutorials

只需要掌握一点,属性由Setter和Getter标识定义。

Methods

方法即是除了属性的Setter和Getter之外的公共方法。如NetBeans的可视化工具将能检视JavaBeans中的方法。

内省

在大致介绍完JavaBeans后,再来看内省就简单多了。Java内省是JavaBeans技术架构体系中的一部分,这点可以参考:JavaBeans Spec

其中是这样描述内省:

At runtime and in the builder environment we need to be able to figure out which properties, events, and methods a Java Bean supports. We call this process introspection.

即运行时获取JavaBean的properties,events和methods的过程称为Java内省。简而言之,即检视JavaBean内部的信息,如方法,属性,事件。主要用来运行时获取JavaBean的内部信息。

### Java内省和反射技术的关系

学习上节关于Java内省技术的介绍,有些读者应该很快联想到Java的反射技术。Java内省和反射技术很类似(反射也具有运行获取Java对象的类型信息)。

文档中是这样描述Java内省和Java反射:

We therefore provide a composite mechanism. By default we will use a low level reflection mechanism to study the methods supported by a target bean and then apply simple design pat- terns to deduce from those methods what properties, events, and public methods are supported. However, if a bean implementor chooses to provide a BeanInfo class describing their bean then this BeanInfo class will be used to programmatically discover the beans behaviour.

为了能够提供JavaBean内部的检视,提供了一组机制用于处理获取JavaBean的内部信息。默认使用低级别的反射机制获取JavaBean的信息。但是对于指定了JavaBean的对应BeanInfo类时,将从BeanInfo中描述的JavaBean信息获取JavaBean的行为,这时将不会再使用反射机制。

所以Java内省和Java反射存在以下关系:

Java反射是一组较低级别的API,使用起来比较复杂。而Java内省提供了一套比较简单和有语义的API用于操作JavaBean; Java内省机制更加简单易用; 由于Java内省底层默认使用Java反射机制,所以同样有Java反射机制带来的性能问题; ### Java内省的API介绍 1.Introspector

The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean

Introspector译名是内省器,它提供了一系列的方法内省获取JavaBean的properties,method和events。

Introspector底层将寻找JavaBean对应的BeanInfo,如果寻找不到,将使用反射机制分析JavaBean。

其中提供getBeanInfo方法可以获取目标JavaBean对应的BeanInfo方法。

2.BeanInfo

BeanInfo是描述JavaBean的信息的一个对象。其中描述了JavaBean的properties,method和event等。但是很多时候是不提供明确的BeanInfo,需要由上述的Introspector使用反射机制获取JavaBean信息生成对应的BeanInfo。

3.BeanDescriptor

A BeanDescriptor provides global information about a "bean", including its Java class, its displayName, etc.

BeanDescriptor是对JavaBean的全局信息描述,描述其Class,名称等。

4.PropertyDescriptor

A PropertyDescriptor describes one property that a Java Bean exports via a pair of accessor methods.

PropertyDescriptor通过一对Setter和Getter方法导出JavaBean的property,是对Property的描述。 PropertyDescriptor提供了获取Setter和Getter方法的接口,获取Property的名称等接口。

5.MethodDescriptor

A MethodDescriptor describes a particular method that a Java Bean supports for external access from other components.

MethodDescriptor用于描述JavaBean中的公共方法,它提供获取该方法对象Method,方法的参数等API。

Java内息机制还提供了一些其他的API,这里只介绍一些常用的重点接口。关于其他的接口,读者有兴趣可以参考:java.beans

下节将通过coding方式学习内息,如何使用这些API避免复杂的反射方式来操作JavaBean。

### Java内省实战

首先编写JavaBean类Person:

/** * person for java bean. * * @author huaijin */ public class Person { private String name; private int age; private List address = new ArrayList(); public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void setAddress(String[] address) { this.address.addAll(Arrays.asList(address)); } public String[] getAddress() { String[] strs = new String[this.address.size()]; return this.address.toArray(strs); } public void setAddress(int index, String value) { this.address.add(index, value); } public String getAddress(int index) { return this.address.get(index); } }

然后再使用上述介绍的API分别来操作JavaBean,内省:

public class IntrospectPerson { public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException { Person person = new Person(); person.setName("huaijin"); person.setAge(18); List address = new ArrayList(); address.add("hangzhou"); address.add("anhui"); person.setAddress(address.toArray(new String[address.size()])); BeanInfo personBeanInfo = Introspector.getBeanInfo(Person.class, Introspector.USE_ALL_BEANINFO); PropertyDescriptor[] propertyDescriptors = personBeanInfo.getPropertyDescriptors(); // 获取PropertyDescriptor,读写属性 for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { System.out.println("property name: " + propertyDescriptor.getName()); System.out.println("read before writing: " + propertyDescriptor.getReadMethod().invoke(person, null)); Class propertyClass = propertyDescriptor.getPropertyType(); if (propertyClass == String.class) { propertyDescriptor.getWriteMethod().invoke(person, "lxy"); } else if (propertyClass == int.class) { propertyDescriptor.getWriteMethod().invoke(person, 28); } if (propertyDescriptor instanceof IndexedPropertyDescriptor) { IndexedPropertyDescriptor indexedPropertyDescriptor = (IndexedPropertyDescriptor) propertyDescriptor; indexedPropertyDescriptor.getIndexedWriteMethod().invoke(person, 0, "2dfire"); } System.out.println("read after writing: " + propertyDescriptor.getReadMethod().invoke(person, null) + "\n"); } // 获取MethodDescriptor,可以操作JavaBean MethodDescriptor[] methodDescriptors = personBeanInfo.getMethodDescriptors(); for (MethodDescriptor methodDescriptor : methodDescriptors) { System.out.println("methodName: " + methodDescriptor.getName()); } } }

可以看出,利用Introspector、BeanInfo和PropertyDescriptor等比使用反射机制带来更多的简便性。

在日常开发中,或许经常使用Java内省而不知,下面就通过日常开发中使用到的案例进一步熟悉Java内息。读者或许经常使用Spring的BeanUtils工具类拷贝JavaBean的属性到另一个目标对象中。BeanUtils.copyProperties内部实现就使用了Java内省机制。

private static void copyProperties(Object source, Object target, Class editable, String... ignoreProperties) throws BeansException { // 断言源对象和目标对象不能为空 Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"); // 获取目标对象的类型 Class actualEditable = target.getClass(); if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } actualEditable = editable; } // 获取目标对象的所有Properties PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); // 遍历目标对象的Properties for (PropertyDescriptor targetPd : targetPds) { // 获取Property的Setter方法 Method writeMethod = targetPd.getWriteMethod(); // 如果Setter方法不为空且Property没有被忽略 if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { // 获取源对象对应的Property PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); // 如果源对象中存在该Property if (sourcePd != null) { // 获取源对象的Property对应的Getter方法 Method readMethod = sourcePd.getReadMethod(); // 如果读方法不为空,且Getter的返回类型和目标对象的Setter方法参数类型匹配 if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { try { // 如果Getter方法非公共可见,则设置Getter方法可访问 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } // 反射调用源对象的Getter方法,获取属性值 Object value = readMethod.invoke(source); // 如果目标对象的Setter方法非公共可见,设置其可访问 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } // 反射调用目标对象的Setter方法,设置属性值 writeMethod.invoke(target, value); } catch (Throwable ex) { throw new FatalBeanException( "Could not copy property '" + targetPd.getName() + "' from source to target", ex); } } } } } }

以上的逻辑便是BeanUtils中利用JavaBeans中的内省技术实现对象的属性拷贝。

从源码中不难返现,其中使用了大量的反射机制。读者应该都知道,大量使用反射必然有一定的性能问题。性能在于:运行时分析类型信息,如Field、Method等,非常消耗性能。这些信息本应该在编译期间解决,在运行时获取虽然带来更大的灵活性,但是也带来不可避免的性能问题。

对于反射的性能问题,常见的手段遍是缓存分析结果信息,如缓存Method,Field等。这种手段在Spring和Java动态代理中都有体现。下面再使用Spring内部的BeanWrapper优化使用JavaBean的内省技术来分析其对性能的优化方式。

@Override public PropertyDescriptor[] getPropertyDescriptors() { // 从缓存中获取PropertyDescriptor return getCachedIntrospectionResults().getPropertyDescriptors(); } private CachedIntrospectionResults getCachedIntrospectionResults() { // 断言被包装的目标Bean不为空 Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance"); // 如果缓存的内省结果为空则进行内省,并缓存 if (this.cachedIntrospectionResults == null) { this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass()); } return this.cachedIntrospectionResults; }

Spring中的BeanWrapper对于Java内省的优化,是遵循Java反射的优化的策略的。即缓存PropertyDescriptor:

private final Map propertyDescriptorCache; ### 总结 Java内省技术提供一套高级API的机制针对JavaBean操作,底层默认通过Java反射机制获取JavaBean的信息。内省机制在Spring中有很多使用场景,如BeanUtils,BeanWrapper等。在日常开发中也可以使用简化反射机制带来的复杂性。 参考

Java introspection and reflection java.beans How to use Reflection, Introspection and Customization in JavaBeans JavaBeans(TM) Specification 1.01 Final Release Trail: JavaBeans(TM)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有